/**
 * @class luffy.common.Util.Bubbler
 * 버블링을 사용한 이벤트 바인딩을 구현하는 클래스
 */
luffy.common.Bubbler = jindo.$Class( {

	/**
	 * @constructor
	 * 
	 * @param {HTMLElement} elEl 어느 엘리먼트를 통해 이벤트를 잡아낼지 지정
	 */
	$init : function(elEl) {

		this._el = elEl;

		this._eventTypes = {};
		this._fpEventHandler = jindo.$Fn(this._eventHandler, this);

	},

	/**
	 * 이벤트 핸들러 등록
	 * 
	 * @param {String} sEvent 이벤트 종류 및 클래스명
	 * @param {Function} fpHandler 이벤트 핸들러
	 */
	attach : function(sEvent, fpHandler) {

		// 객체 형태로 인자를 지정했을때는 루프를 돌아 모두 등록시킨다
		if (typeof sEvent == 'object') {

			var fp = arguments.callee;

			jindo.$H(sEvent).forEach( function(f, k) {
				fp.call(this, k, f);
			}, this);

			return this;
		}

		// 이벤트명과 클래스명을 잘라냄
		var aParsed = sEvent.split(':');

		var sClassName = aParsed[0];
		var sType = aParsed[1].toLowerCase();

		// mouseenter 와 mouseleave 이벤트는 mouseover, mouseout 이벤트를 통해 구현해야 함
		var sRealType = sType;

		if (sRealType == 'mouseenter')
			sRealType = 'mouseover';
		else if (sRealType == 'mouseleave')
			sRealType = 'mouseout';

		// 이벤트 바인딩이 안되어 있으면
		if (!(sType in this._eventTypes)) {
			this._eventTypes[sType] = jindo.$H();
			this._fpEventHandler.attach(this._el, sRealType);
		}

		var whClassNames = this._eventTypes[sType];

		// 해당 클래스가 등록되어 있지 않으면
		if (!whClassNames.hasKey(sClassName)) {
			whClassNames.$(sClassName, jindo.$A());
		}

		// 핸들러 추가
		whClassNames.$(sClassName).push(fpHandler);

		return this;

	},

	/**
	 * 이벤트 핸들러 등록 해제
	 * 
	 * @param {String} sEvent 이벤트 종류 및 클래스명
	 * @param {Function} fpHandler 이벤트 핸들러
	 */		
	detach : function(sEvent, fpHandler) {

		// 객체 형태로 인자를 지정했을때는 루프를 돌아 모두 등록 해제시킨다
		if (typeof sEvent == 'object') {
			var fp = arguments.callee;
			jindo.$H(sEvent).forEach( function(f, k) {
				fp.call(this, k, f);
			}, this);

			return this;
		}

		// 이벤트명과 클래스명을 잘라냄
		var aParsed = sEvent.split(':');

		var sClassName = aParsed[0];
		var sType = aParsed[1].toLowerCase();

		var whClassNames = this._eventTypes[sType];
		var waHandlers = whClassNames.$(sClassName);

		// 핸들러 목록에서 제거
		if (waHandlers) {

			var nIndexOf = waHandlers.indexOf(fpHandler);
			if (nIndexOf != -1)
				waHandlers.splice(nIndexOf, 1);

		}

	},

	/**
	 * 등록한 이벤트가 발생했을때 호출되는 메서드
	 * 
	 * @param {$Event} oEvent 이벤트 객체 
	 */
	_eventHandler : function(oEvent) {

		var sType = oEvent.type.toLowerCase();
		var sAltType = null;

		// mouseover, mouseout 이벤트인 경우 mouseenter, mouseleave 에 대한 처리를 해야함
		if (sType == 'mouseover')
			sAltType = 'mouseenter';
		else if (sType == 'mouseout')
			sAltType = 'mouseleave';

		var whClassNames = this._eventTypes[sAltType || sType];

		if (whClassNames) {

			var elSrc = oEvent.element;
			var elRelSrc = oEvent.relatedElement;

			// 해당 이벤트가 등록된 클래스를 루프돔 
			whClassNames.forEach( function(waHandlers, sClassName) {

				// 이벤트가 발생한 엘리먼트가 해당 클래스명을 가졌는지 검사
				var elTar = this.mapGetParentByClass(elSrc, sClassName);
				if (!elTar)
					jindo.$H.Continue();

				// mouseenter, mouseleave 이벤트에 대한 판단을 해야하는 경우
				if (sAltType) {
					var elRelTar = this.mapGetParentByClass(elRelSrc, sClassName);
					
					// 대상 안에서 mouseover, mouseout 이 발생한거면 무시
					if (elTar === elRelTar)
						jindo.$H.Continue();
				}

				var aArgs = this.mapGetClass(elTar, sClassName);

				// 등록된 이벤트 핸들러 호출
				waHandlers.forEach( function(fpHandler) {
					fpHandler(oEvent, elTar, aArgs);
				});

				elTar = null;

			}, this);

		}

		oEvent = null;

	},
	
	mapGetParentByClass : function(elEl, sClassName) {

		var regExp = new RegExp(
				'(^|\\s+)' + sClassName + '(\\(([^\)]*)\\))?(\\s+|$)', 'i');
		
		var bHasClass = false;
		try { bHasClass = 'className' in elEl; } catch(e) {};

		for (; elEl && bHasClass; elEl = elEl.parentNode) {
			if (regExp.test(elEl.className)) {
				RegExp.$0 = RegExp.$3;
				return elEl;
			}
		}

		return null;

	},
	mapGetClass : function(elEl, sClassName) {

		var regExp = new RegExp(
				'(^|\\s+)' + sClassName + '(\\(([^\)]*)\\))?(\\s+|$)', 'i');
		
		var bFlag = regExp.test(elEl.className); 
		RegExp.$0 = RegExp.$3;
		
		if (bFlag)
			return (RegExp.$3 || '').split(',');

		return null;

	}

});